package com.ColonyCount;

import java.nio.ByteBuffer;
import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import com.ColonyCount.Image.EDM;
import com.ColonyCount.Image.Histogram;
import com.ColonyCount.Image.Image16;
import com.ColonyCount.Image.Image8;
import com.ColonyCount.Image.ImageHelper;
import com.ColonyCount.Image.ImageRegion;
import com.ColonyCount.Image.Segmentation;

import android.app.Activity;
import android.app.ActivityManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.util.Log;

public class ColonyCountContext {

    static ColonyCountContext singleton;
    
    public static final int minRegionSize = 5;	// min region size
    
    public static ColonyCountContext getSingleton()
    {
    	if(singleton == null)
    	{
    		singleton = new ColonyCountContext();
    	}
    	return singleton;
    }
    
    String fileName;

	int threshold = 85;
	Rect clipBounds;
	
	public void setImageFileName(String fileName)
	{
		this.fileName = fileName;
		clipBounds = null;
	}
	
	public void setClipBounds(Rect rect)
	{
		clipBounds = rect;
	}
	
	public Rect getClipBounds()
	{
		return clipBounds;
	}
	
	static public void dumpActivityEvent(Activity activity, String name)
	{
//    	dumpMemoryStats(activity, name);
	}
	
	public Rect getRegionBounds()
	{
		return clipBounds;
	}

	public void createSourceBitmap(Activity activity)
	{
		ColonyCountHeap.freeLumaBitmap();
		ColonyCountHeap.freeLumaMap();
		ColonyCountHeap.freeSourceBitmap();
		
		// Get source size
		BitmapFactory.Options bounds = new BitmapFactory.Options();
		bounds.inJustDecodeBounds = true;
		BitmapFactory.decodeFile(fileName, bounds);
		int sourceWidth = bounds.outWidth;
		
		// Get sample size
		int sampleSize = 1;
		int maxWidth = Preferences.getMaxImageWidth(activity);
		maxWidth = 2000;
		if(sourceWidth > maxWidth)
		{
		    float fSampleSize = (float) sourceWidth / (float) maxWidth;
		    sampleSize = (int)Math.ceil(fSampleSize);
		}
		
		// Load source bitmap
		BitmapFactory.Options factoryOptions = new BitmapFactory.Options();
		factoryOptions.inSampleSize = sampleSize;
		factoryOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;
		Bitmap sourceBitmap = BitmapFactory.decodeFile(fileName, factoryOptions);
		if(sourceBitmap == null)
		{
			throw new InvalidParameterException("cant find or decode file: " + fileName);
		}
		
		ColonyCountHeap.setSourceBitmap(sourceBitmap);
		
		// Reset clip bounds
		
		if(clipBounds == null)
		{
			int centerX = sourceBitmap.getWidth()/2;
			int centerY = sourceBitmap.getHeight()/2;
			int radius = Math.min(centerX, centerY) - 30;
			clipBounds = new Rect(centerX - radius, centerY - radius, centerX + radius, centerY + radius);
		}
		

		
//		{
//			int rowBytes = sourceBitmap.getRowBytes();
//			int bmWidth = sourceBitmap.getWidth();
//			byte[] luma = new byte[rowBytes*bmWidth];
//			ByteBuffer lumaBuf = ByteBuffer.wrap(luma);
//			sourceBitmap.copyPixelsToBuffer(lumaBuf);
//			
//			int i=0;
//			i++;
//		}

	}
	
	public void createLuma()
	{
		Log.v("ColonyCount", "ColonyCountCOntext.createLuma()");
		
		long time = System.currentTimeMillis();
		
		int regionWidth = clipBounds.right - clipBounds.left;
		int regionHeight = clipBounds.bottom - clipBounds.top;
		
		ColonyCountHeap.freeLumaBitmap();
		ColonyCountHeap.freeLumaMap();
		
		ColonyCountHeap.logHeapState();

		// Create BW image
		Bitmap regionBitmap = Bitmap.createBitmap((int)regionWidth,(int) regionHeight, Bitmap.Config.ARGB_8888);
		Canvas canvas = new Canvas(regionBitmap);
		ColorMatrix colorMatrix = new ColorMatrix();
		colorMatrix.setSaturation(0);
		Paint saturationPaint = new Paint();
		saturationPaint.setColorFilter(new ColorMatrixColorFilter(colorMatrix));
		canvas.drawBitmap(ColonyCountHeap.getSourceBitmap(), clipBounds, new Rect(0,0,(int)regionWidth,(int)regionHeight), saturationPaint);
		ColonyCountHeap.freeSourceBitmap();
		
		// Filter out ROI
//		Path clipPath = new Path();
//		RectF rect = new RectF(0,0, regionWidth, regionHeight); 
//		clipPath.addOval(rect,  Path.Direction.CCW);
//		canvas.clipPath(clipPath, Region.Op.DIFFERENCE);
//		canvas.drawColor(0xFFFFFFFF);

		
		// Create histogram
		int[] histogramValues = new int[256];
		
		int rowBytes = regionBitmap.getRowBytes();
		int bmHeight = regionBitmap.getHeight();
		byte[] regionArray = new byte[rowBytes*bmHeight];
		ByteBuffer regionBuf = ByteBuffer.wrap(regionArray);
		regionBitmap.copyPixelsToBuffer(regionBuf);
		
		Image8 lumaMap = new Image8(regionBitmap.getWidth(), regionBitmap.getHeight());
		byte[] lumaPixes = lumaMap.getPixels();
		for(int y=0;y<regionHeight;y++)
		{
			for(int x=0;x<regionWidth;x++)
			{
				int regionOffset = x*4 + y*rowBytes;
				int lumaOffset = x + y*(int)regionWidth;
				int iVal = regionArray[regionOffset] & 0xFF;
				histogramValues[iVal] ++;
				lumaPixes[lumaOffset] = (byte) (iVal);
			}
		}
		
		histogramValues[255] = 0;
		
		regionArray = null;
		regionBuf = null;
		System.gc();
		
		ColonyCountHeap.setLumaMap(lumaMap);
		ColonyCountHeap.setLumaBitmap(regionBitmap);
		ColonyCountHeap.setHistogram(new Histogram(histogramValues));  

		Log.v("ColonyCount", "createLuma() done " + (System.currentTimeMillis() - time)+ "ms");
	}	
	
	
	public void identifyRegions()
	{
		Log.v("ColonyCount", "ColonyCountCOntext.identifyRegions()");
		
		ColonyCountHeap.logHeapState();
	
		Image8 lumaMap = ColonyCountHeap.getLumaMap();
		Image8 areaMap = new Image8(lumaMap.getWidth(), lumaMap.getHeight());
		byte[] lumaPixels = lumaMap.getPixels();
		byte[] areaPixels = areaMap.getPixels();
		int count = lumaMap.getWidth()*lumaMap.getHeight();
		for(int i=0;i<count;i++)
		{
			int iVal = lumaPixels[i] & 0xff;
			
			if(iVal < threshold)
			{
				areaPixels[i] = 1;
			}
		}
		
	    
		List<ImageRegion> regions = ColonyCountHeap.getRegions();

		regions.clear();
		
		getRegions(areaMap, regions);
		
		double tolerance = 1;
		double threshold = 1; // min maxima value
		boolean isEDM = true;
		Image16 distanceMap;
		Image8 maxMap;
		Image8 regionMap;
		Segmentation seg = new Segmentation();

		List<ImageRegion> regionsCopy = new ArrayList<ImageRegion>(regions);
		for(int i=0;i<regionsCopy.size();i++)
		{
			ImageRegion region = regionsCopy.get(i);
			Rect bounds = region.getBounds();
			int width = bounds.right - bounds.left + 1;
			int height = bounds.bottom - bounds.top + 1;
			distanceMap = new Image16(width, height);
			maxMap = new Image8(width, height);
			regionMap = new Image8(width, height);
			
			EDM.createEDM(region, distanceMap);

			seg.findMaxima(distanceMap, tolerance, threshold, maxMap, isEDM);
			seg.performWatershed(distanceMap, threshold, maxMap, isEDM, regionMap);

			List<ImageRegion> newRegions = new ArrayList<ImageRegion>();
			getRegions(regionMap, newRegions);
			if(newRegions.size() > 1)
			{
				regions.remove(region);
				for(int iNewRegion=0;iNewRegion<newRegions.size();iNewRegion++)
				{
					newRegions.get(iNewRegion).offset(bounds.left, bounds.top);
				}
				regions.addAll(newRegions);
			}
		}
		
		
		
		
		
//		ColonyCountHeap.setDistanceMap(new Image16(getRegionWidth(), getRegionHeight()));
//
//		EDM.createEDM(ColonyCountHeap.getLumaMap(), thresholdMin, thresholdMax, ColonyCountHeap.getDistanceMap());
//		
//		ColonyCountHeap.freeLumaMap();
//		ColonyCountHeap.logHeapState();
//		
//		//debugSleep();
//
//		ColonyCountHeap.setMaxMap(new Image8(getRegionWidth(), getRegionHeight()));
//
//		double tolerance = 1;
//		double threshold = 1; // min maxima value
//		boolean isEDM = true;
//		Segmentation seg = new Segmentation();
//		seg.findMaxima(ColonyCountHeap.getDistanceMap(), tolerance, threshold, ColonyCountHeap.getMaxMap(), isEDM);
//		
//		ColonyCountHeap.setRegionMap(new Image8(getRegionWidth(), getRegionHeight()));
//		
//		seg.performWatershed(ColonyCountHeap.getDistanceMap(), threshold, ColonyCountHeap.getMaxMap(), isEDM, ColonyCountHeap.getRegionMap());
//		
//		regions.clear();
//		System.gc();
//		identifyRegions(ColonyCountHeap.getRegionMap(), regions);
	}
	
	
	void getRegions(Image8 regionMap, List<ImageRegion> regions)
	{
		Random rnd = new Random();
		byte[] areaPixels = regionMap.getPixels();
		int left = regionMap.getWidth() - 1;
		for(int mapY=1;mapY<regionMap.getHeight() - 1;mapY++)
		{
			for(int mapX=1;mapX<left;mapX++)
			{
				int offset = mapX + mapY*regionMap.getWidth();
				int iVal = areaPixels[offset] & 0xff;
				if(iVal > 0)
				{
					List<Point> points = new ArrayList<Point>();
					ImageHelper.floodLoop(regionMap, mapX, mapY, 0, iVal, points);
					
					if(points.size() >= minRegionSize)
					{
						Point[] p = new Point[points.size()];
						points.toArray(p);
						
						float hue = rnd.nextFloat()*360.0f;
						float value = rnd.nextFloat()*0.7f + 0.3f;
						float[] hsv = { hue, 1.0f, value };
						int color = Color.HSVToColor (hsv);
						
						regions.add(new ImageRegion(p, color));
					}
				}
			}
		}
	}

	public void debugSleep() 
	{
		try {
			Thread.sleep(100000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

    public void setThreshold(int threshold)
    {
    	this.threshold = threshold;
    }
    
    public int getThreshold()
    {
    	return threshold;
    }
}
